# @Time : 2020/7/27 16:30
# @Author : Fioman 
# @Phone : 13149920693
import cv2 as cv
import numpy as np

"""
图像梯度计算的是图像变化的速度.对于图像的边缘部分,其灰度值变化较大,梯度值也较大;
相反,对于图像中比较平滑的部分,其灰度值变化较小,相应的梯度值也较小.严格来说,图像的梯度计算需要求导数,但是图像梯度一般通过计算像素值的差来得到梯度的
近似值(近似导数值).
Sobel算子是一种离散的微分算子,该算子结合了高斯平滑和微分求导运算.
Sobel算子利用局部差分寻找边缘,计算所得到的是一个梯度的近似值.
需要说明的是,滤波器通常是指由一副图像根据像素点(x,y)临近的区域计算得到另外一副新图像的算法.因此,滤波器是由邻域以及预定义的操作构成的.
滤波器规定了滤波时所采用的形状以及该区域内像素值的组成规律.
也就是说,滤波的目标像素点的值等于原始像素值以及周围像素值的加权和.这种基于线性核的滤波,就是我们说熟悉的卷积.
计算水平方向偏导数的近似值:
将Sobel算子与原始图像src进行卷积计算,可以计算水平方向上的像素值变化情况.例如,当Sobel算子的大小为3*3时,水平方向偏导数Gx的计算方式为:
Gx = kernel*src
src是原始图像,假设其中有9个像素点,如图9-3所示:
某个点的水平方向的偏导数,就等于这个点的9宫格里面的 上下分别是1倍的右边-左边, 中间是两倍的右边-左边,然后再将这些结果相加得到.
垂直方向的偏导数是下面-上面 正对垂直方向离中心点最近,所以是2倍的下面-上面,然后将结果求和即可.

dst = cv.Sobel(src,ddepth,dx,dy,ksize,scale,delta borderType)
dx 代表x方向上的求导阶数
dy 表示y方向上的求导阶数
ksize表示Sobel核的大小,该值为-1的时候,则会使用Scharr算子进行计算.
scale代表计算导数值时所采用的缩放因子,默认情况下该值是1,是没有缩放的.
delta代表加载目标图像dst上的值,该值是可选的,默认值为0

ddepth参数设置为-1,表示目标图像与原始图像保持一致.
在实际的操作中,计算梯度值可能会出现负数.如果处理的图像是8位图类型,则在ddepth的参数值为-1的时候,意味着指定运算结果也是8位图类型.
那么所有负数都会自动截断为0,发生信息丢失.为了避免信息丢失,在计算时要先使用更高的数据类型cv.CV_64F,再公国取绝对值将其映射为cv.CV_8U(8位图类型)
所以,通常要将函数cv.Sobel()参数的ddepth的值设置为cv.CV_64F.

为了可以让偏导数正确地显示出来,需要将值为负数的近似偏导数转换为正数.即,要将偏导数取绝对值,以保证偏导数总能正确地显示出来.
在实际的操作中,计算梯度值可能会出现负数.通常处理的图像是8位图类型,如果结果也是该类型,那么所有负数会自动截断为0,发生信息丢失.
所以,为了避免信息丢失,我们再计算时使用更高的数据类型cv.CV_64F,再通过取绝对值将其映射为cv.CV_8U(8位图)
cv.convertScaleAbs(src,alpha,beta)
dst代表处理结果, src代表原始图像, alpha代表调节系数,该值是可选值,默认值1.
beta代表调节亮度值,该值默认值是0
这里,该函数的作用是将原始图像src转换为256色位图,其可以表示为:
dst = saturate(src*alpha + beta)
saturate()表示计算结果的最大值是饱和值,例如当"src*alpha+beta"的值超过255的时候,其取值255.
"""
img = np.random.randint(-300, 400, size=[4, 5], dtype=np.int32)
rst = cv.convertScaleAbs(img)  # 无论原来的数据是什么类型,最终都会转换为0~255之间的值.
print("img = \n", img)
print("rst = \n", rst)

"""
方向:
在函数cv.Sobel()中,参数dx表示x轴方向的求导阶数,参数dy表示y轴方向的求导阶数.参数dx和dy通常的值为0或者1,最大值为2.
如果是0,表示在该方向上没有求导.当然,参数dx和参数dy的值不能同时为0.
计算x方向的边缘梯度: dx=1,dy=0
计算y方向的边缘梯度: dx=0,dy=1
x方向和y方向的梯度同时求: dx=1,dy=1
计算x方向和y方向的边缘叠加: 通过组合的方式实现.
"""
img = cv.imread("sobel.jpg", cv.IMREAD_GRAYSCALE)
sobelX = cv.Sobel(img, ddepth=cv.CV_64F, dx=1, dy=0)
sobelX = cv.convertScaleAbs(sobelX)

sobelY = cv.Sobel(img, ddepth=cv.CV_64F, dx=0, dy=1)
sobelY = cv.convertScaleAbs(sobelY)
sobelXY = cv.Sobel(img, cv.CV_64F, dx=1, dy=1)
sobelXANDY = cv.addWeighted(sobelX, 0.5, sobelY, 0.5, 0)

cv.imshow("Original", img)
cv.imshow("SobelX", sobelX)
cv.imshow("SobelY", sobelY)
cv.imshow("SobelXY", sobelXY)
cv.imshow("SobelXANDY", sobelXANDY)
cv.waitKey(0)
cv.destroyAllWindows()

img = cv.imread("test.bmp", cv.IMREAD_GRAYSCALE)
sobelX = cv.Sobel(img, ddepth=cv.CV_64F, dx=1, dy=0)
sobelX = cv.convertScaleAbs(sobelX)
sobelY = cv.Sobel(img, ddepth=cv.CV_64F, dx=0, dy=1)
sobelY = cv.convertScaleAbs(sobelY)

sobelXY = cv.addWeighted(sobelX, 1, sobelY, 1, 0)
cv.imshow("Original", img)
cv.imshow("SobelX", sobelX)
cv.imshow("SobelY", sobelY)
cv.imshow("SobelXY", sobelXY)
kernel = cv.getStructuringElement(cv.MORPH_RECT, (7, 7))
closed = cv.morphologyEx(sobelXY, cv.MORPH_CLOSE, kernel, iterations=5)
cv.imshow("Closed", closed)

T, thresBoard = cv.threshold(closed, 0, 255, cv.THRESH_BINARY | cv.THRESH_OTSU)
cv.imshow("ThresBoard", thresBoard)

opened = cv.morphologyEx(thresBoard, cv.MORPH_CLOSE, kernel, iterations=5)
cv.imshow("Opened", opened)

cv.waitKey(0)
cv.destroyAllWindows()
